﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace Commander
{
    /// <summary>
    /// 模拟终端
    /// </summary>
    public class Terminal : IDisposable
    {
        // 私有变量
        private readonly StreamWriter _input;
        private readonly StreamReader _output;
        private readonly StreamReader _error;
        private const int Char_Enter = (byte)'\n';
        private readonly Process _process;
        private const string Normal_Chars = "~!@#$%^&*()_+-=QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm{}:|\"<>?[]\\;',./1234567890";

        /// <summary>
        /// 是否运行中
        /// </summary>
        public bool IsRunning { get; private set; }

        /// <summary>
        /// 输出内容
        /// </summary>
        public List<string> Ouputs { get; private set; }

        /// <summary>
        /// 线程信息
        /// </summary>
        public Process Process { get { return _process; } }

        /// <summary>
        /// 清空
        /// </summary>
        public void Clear()
        {
            for (int i = this.Ouputs.Count - 2; i >= 0; i--)
            {
                this.Ouputs.RemoveAt(i);
            }
        }

        /// <summary>
        /// 运行命令
        /// </summary>
        /// <param name="cmd"></param>
        public void RunCommand(string cmd)
        {
            _input.WriteLine(cmd);
        }

        // 检测并创建新行
        private int CheckOrCreateLine(int line)
        {
            if (line < 0)
            {
                this.Ouputs.Add("");
                line = this.Ouputs.Count - 1;
            }
            return line;
        }

        // 读取
        private int Read(StreamReader reader, List<int> chars, StringBuilder sb, int line)
        {
            int res = reader.Read();
            if (res > 255)
            {
                // 处理双字节
                sb.Append((char)res);
                line = CheckOrCreateLine(line);
                this.Ouputs[line] = sb.ToString();
            }
            else if (chars.Contains(res))
            {
                // 处理常规字符
                sb.Append((char)res);
                line = CheckOrCreateLine(line);
                this.Ouputs[line] = sb.ToString();
            }
            else
            {
                switch (res)
                {
                    case Char_Enter:
                        // 回车
                        this.Ouputs.Add("");
                        line = this.Ouputs.Count - 1;
                        sb.Clear();
                        break;
                    case 0x09:
                        // Tab
                        line = CheckOrCreateLine(line);
                        var len = this.Ouputs[line].Length;
                        sb.Append(new string(' ', 8 - len % 8));
                        this.Ouputs[line] = sb.ToString();
                        break;
                    case '\r':
                    case ' ':
                        // 常规处理
                        sb.Append((char)res);
                        line = CheckOrCreateLine(line);
                        this.Ouputs[line] = sb.ToString();
                        break;
                    default:
                        sb.Append($"[0x{res.ToString("x2")}]");
                        sb.Append((char)res);
                        line = CheckOrCreateLine(line);
                        this.Ouputs[line] = sb.ToString();
                        break;
                }
            }
            return line;
        }

        /// <summary>
        /// 模拟终端
        /// </summary>
        public Terminal(string path)
        {
            List<int> chars = new List<int>();
            for (int i = 0; i < Normal_Chars.Length; i++) chars.Add(Normal_Chars[i]);
            System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);
            //System.Console.InputEncoding = System.Text.Encoding.UTF8;
            //Encoding gb2312 = Encoding.GetEncoding("GB2312");
            Encoding encoding = Encoding.UTF8;
            this.Ouputs = new List<string>();
            ProcessStartInfo processStartInfo = new ProcessStartInfo("powershell.exe");
            processStartInfo.UseShellExecute = false;
            processStartInfo.RedirectStandardOutput = true;
            processStartInfo.RedirectStandardError = true;
            processStartInfo.RedirectStandardInput = true;
            processStartInfo.CreateNoWindow = true;
            processStartInfo.WorkingDirectory = path;
            processStartInfo.StandardInputEncoding = encoding;
            processStartInfo.StandardOutputEncoding = encoding;
            processStartInfo.StandardErrorEncoding = encoding;
            var process = Process.Start(processStartInfo);
            if (process is null) throw new CommandException($"程序'{processStartInfo.FileName}'执行失败");
            _process = process;
            this.IsRunning = true;
            this.Ouputs.Clear();
            _input = _process.StandardInput;
            _output = _process.StandardOutput;
            _error = _process.StandardError;
            int outputLine = -1;
            int errorLine = -1;
            StringBuilder sbOutput = new StringBuilder();
            StringBuilder sbError = new StringBuilder();
            //this.Ouputs.Add("");
            Thread thread = new Thread(() =>
            {
                while (!_process.HasExited)
                {
                    outputLine = Read(_output, chars, sbOutput, outputLine);
                }
            });
            thread.Start();
            Thread thread2 = new Thread(() =>
            {
                while (!_process.HasExited)
                {
                    errorLine = Read(_error, chars, sbError, errorLine);
                }
            });
            thread2.Start();
        }

        // 退出
        private void Process_Exited(object? sender, EventArgs e)
        {
            this.IsRunning = false;
        }

        /// <summary>
        /// 释放资源
        /// </summary>
        public void Dispose()
        {
            _process?.Kill();
            GC.SuppressFinalize(this);
        }
    }
}
